28.6 Using the Quartz Scheduler使用Quartz任务调度

Quartz使用TriggerJobJobDetail来实现各种调度任务。Quartz的基础概念可以查看http://quartz-scheduler.org。为了方便,Spring提供了在Spring基础应用下简单使用Quartz的数个类。

28.6.1 Using the JobDetailFactoryBean

Quartz JobDetail对象包含了执行一个工作任务的所有必要信息。Spring提供JobDetailFactoryBean以XML格式配置Bean样式的属性。让我们来看一个例子:

<bean name="exampleJob" class="org.springframework.sheduling.quartz.JobDetailFactoryBean">
    <property name="jobClass" value="example.ExampleJob" />
    <property name="jobDataAsMap">
        <map>
            <entry key="timeout" value="5" />
        </map>
    </property>
</bean>

工作任务细节配置包含了执行的所有需要的信息(ExampleJob)。timeout是在工作任务数据以键值形式被指定。工作任务数据键值可以通过JobExecutionContext工作执行上下文获取(在执行期间传递),但是JobDetail也是通过工作实例被数据映射的属性来得到它的属性值。所以在这个例子中,如果ExampleJob包含了一个名称为timeout的bean属性,JobDetail将会自动获取到:

package example;

public class ExampleJob extends QuarztJobBean {
    private int timeout;

    /**
     * Setter called after the ExampleJob is instantiated
     * with the value from the JobDetailFactoryBean(5)
     */
    public void setTimeout(int timeout) {
        this.timeout = timeout;
    }

    protected void executeInternal(JobExecutionContext ctx) throws JobExecutionException {
        // do the actual work
    }
}

通过工作数据键值配置的所有附件属性也都是可用的。

使用namegroup属性,可以分别改变工作的名称和组。工作的名称默认是匹配JobDetailFactoryBean的名称(在上面的例子中,就是exampleJob)

28.6.2 Using the JobDetailFactoryBean

经常需要调用特定对象的方法。使用MethodInvokingJobDetailFactoryBean可以实现这一点:

<bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
    <property name="targetObject" ref="exampleBusinessObject" />
    <property name="targetMethod" value="doIt" />
</bean>

上面的例子将会调用exampleBusinessObject对象的doIt方法:

public class ExampleBusinessObject {

// properties and collaborators

public void doIt() {
    // do the actual work
}

}

<bean id="exampleBusinessObject" class="examples.ExampleBusinessObject" />

使用MethodInvokingJobDetailFactoryBean,不需要去创建一个仅仅实现调用方法的主工作任务,只需创建真正业务逻辑对象和关注对象实现的细节。

Quartz工作任务默认是无状态的,导致的结果工作任务之间会相互干扰。如果给同一个JobDetail指定两个触发器,可能会出现在第一个任务完成之前,第二个任务就要开始了。如果JobDetail类实现了Stateful接口,这种情况就不会发生。在第一个任务完成之前第二个任务将不会开始。为了使任务通过MethodInvokingJobDetailFactoryBean不并发,设置concurrent标志为false

<bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
    <property name="targetObject" ref="exampleBusinessObject" />
    <property name="targetMethod" value="doIt" />
    <property name="concurrent" value="false" />
</bean>

默认的工作任务是在并发形式下执行的

28.6.3 Wiring up jobs using triggers and the SchedulerFactoryBean

我们已经创建了工作任务和任务的实现。也有方便的bean来调用指定的对象方法。我们仍然需要这些工作任务自己进行调度。使用触发器和SchedulerFactoryBean来完成。Quartz实现了几个触发器,Spring提供两个方便的Quartz FactoryBean实现:CronTriggerFactoryBeanSimpleTriggerFactoryBean

触发器是需要被安排调用的。Spring提供SchedulerFactoryBean使触发器做可以设置的属性暴露出来。SchedulerFactoryBean使用触发器来安排调用工作任务。

下面是两个例子:

<bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
    <!-- see the example of method invoking job above -->
    <property name="jobDetail ref="jobDetail" />
    <!-- 10 seconds -->
    <property name="startDelay" value="10000" />
    <!-- repeat every 50 seconds -->
    <property name="repeatInterval" value="50000" />
</bean>

<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
    <property name="jobDetail" ref="exampleJob" />
    <!-- run every morning at 6 AM -->
    <property name="cronExpression" value="0 0 6 * * ?" />
</bean>

现在我们已经设置了两个触发器,一个每隔50秒延迟10秒启动一个次和一个每天在上6点执行。最后我们需要设置SchedulerFactoryBean:

<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
    <property name="triggers">
        <list>
            <ref bean="cronTrigger" />
            <ref bean="simpleTrigger" />
        </list>
    </property>
</bean>

SchedulerFactoryBean更多的可设置属性,例如工作任务实现使用的calendars,Quartz自定义的属性等等。查看SchedulerFactoryBean的javadocs了解更多信息。